package org.six11.ss.client.olive;

import java.awt.geom.AffineTransform;
import java.awt.geom.RoundRectangle2D;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.net.MalformedURLException;

import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.six11.util.Debug;
import org.six11.util.gui.Colors;
import org.six11.util.gui.Components;
import org.six11.util.gui.Strokes;

/**
 * 
 **/
public class OliveDrawingSurface extends JComponent {

  // border and background UI variables
  private Color bgColor;
  private Color penEnabledBorderColor;
  private Color penDisabledBorderColor;
  private double borderPad;
  private OliveMouseThing mouseThing;
  private boolean penEnabled = false;

  private double xScale, yScale;

  // storage for all raw strokes
  private OliveState state;

  public OliveState getState() {
    return state;
  }

  // renderer
  private SequenceRenderer renderer;

  /**
   * Make an Olive3 object, but do not show it.
   */
  public OliveDrawingSurface(OliveState state) {
    this.state = state;
    xScale = 1.0;
    yScale = 1.0;
    try {
      initGUI();
    } catch (MalformedURLException ex) {
      ex.printStackTrace();
    }
  }

  private final void initGUI() throws MalformedURLException {
    // establish border and background variables
    bgColor = Colors.getDefault().get(Colors.BACKGROUND);
    penEnabledBorderColor = Colors.getDefault().get(Colors.ACCENT);
    penDisabledBorderColor = Colors.getDefault().get(Colors.SELECTED_BG_INACTIVE);
    borderPad = 3.0;

    // connect state to mouse events to record raw user input
    state.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        repaint();
      }
    });

    // establish a default renderer
    renderer = new BasicRenderer();

    // make a stroke listener that listens for pen data from the server
    // xmit.getStreamHandler().registerAsynchronousHandler(MessageNames.MSG_PEN,
    // new ForeignPenHandler(state));

    // establish mouse/pen input handler
    mouseThing = new OliveMouseThing(state);
    setPenEnabled(true);
  }

  /**
   * Changes the read/write mode of the drawing surface. This is a property of the drawing surface
   * instead of the OliveState because multiple surfaces may share pen data but have different
   * penEnabled settings.
   * 
   * @param v
   */
  public void setPenEnabled(boolean v) {
    if (penEnabled != v) {
      if (v) {
        addMouseListener(mouseThing);
        addMouseMotionListener(mouseThing);
      } else {
        removeMouseListener(mouseThing);
        removeMouseMotionListener(mouseThing);
      }
      penEnabled = v;
      repaint();
    }
  }

  public double getXScale() {
    return xScale;
  }

  public void setXScale(double scale) {
    if (scale > 1.0 || scale < 0.0) {
      throw new IllegalArgumentException("X scale must be in the range (0..1)");
    }
    xScale = scale;
  }

  public double getYScale() {
    return yScale;
  }

  public void setYScale(double scale) {
    if (scale > 1.0 || scale < 0.0) {
      throw new IllegalArgumentException("Y scale must be in the range (0..1)");
    }
    yScale = scale;
  }

  public void paintComponent(Graphics g1) {

    // TODO: Painting the component should cache image buffers whenever possible rather than
    // drawing things directly every time. This is a lower-priority thing, but it will be important
    // eventually, as sketches can become large, and the time to render them should always be at
    // interactive speeds (even when the user is working quickly).

    Graphics2D g = (Graphics2D) g1;
    drawBorderAndBackground(g);
    AffineTransform xform = g.getTransform();
    g.scale(getXScale(), getYScale());
    synchronized (state.getSequences()) {
      renderer.renderSequences(g, state.getSequences());
    }
    g.setTransform(xform);
  }

  @SuppressWarnings("unused")
  private void bug(String what) {
    Debug.out("OliveDrawingSurface", what);
  }

  protected void drawBorderAndBackground(Graphics2D g) {
    Components.antialias(g);
    RoundRectangle2D rec = new RoundRectangle2D.Double(borderPad, borderPad, getWidth() - 2.0
        * borderPad, getHeight() - 2.0 * borderPad, 40, 40);
    g.setColor(bgColor);
    g.fill(rec);
    g.setStroke(Strokes.DASHED_BORDER_STROKE);
    g.setColor(penEnabled ? penEnabledBorderColor : penDisabledBorderColor);
    g.draw(rec);
  }
}
